package com.wxy.favorites.service;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.wxy.favorites.config.AppConfig;
import com.wxy.favorites.constant.EmailConstants;
import com.wxy.favorites.constant.ErrorConstants;
import com.wxy.favorites.constant.LogConstants;
import com.wxy.favorites.constant.PublicConstants;
import com.wxy.favorites.core.ApiResponse;
import com.wxy.favorites.core.BizException;
import com.wxy.favorites.core.NoRollbackException;
import com.wxy.favorites.core.PageInfo;
import com.wxy.favorites.dao.*;
import com.wxy.favorites.dto.*;
import com.wxy.favorites.entity.*;
import com.wxy.favorites.security.ContextUtils;
import com.wxy.favorites.security.SecurityUser;
import com.wxy.favorites.util.*;
import com.wxy.favorites.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import javax.persistence.criteria.Predicate;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author wangxiaoyuan
 * @Date 2020/4/24 11:50
 * @Description
 **/
@Slf4j
@Service
@Transactional(noRollbackFor = NoRollbackException.class)
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private FavoritesRepository favoritesRepository;

    @Autowired
    private MomentRepository momentRepository;

    @Autowired
    private SearchTypeRepository searchTypeRepository;

    @Autowired
    private TaskRepository taskRepository;

    @Autowired
    private MemorandumRepository memorandumRepository;

    @Autowired
    private QuickNavigationRepository quickNavigationRepository;

    @Autowired
    private PasswordRepository passwordRepository;

    @Autowired
    private CategoryRepository categoryRepository;

    @Autowired
    private FeedbackRepository feedbackRepository;

    @Autowired
    private UserFileRepository userFileRepository;

    @Autowired
    private TokenUtils tokenUtils;

    @Autowired
    private AppConfig appConfig;

    @Autowired
    private SystemLogRepository systemLogRepository;

    @Autowired
    private EmailUtils emailUtils;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private VerificationService verificationService;

    @Autowired
    private ClockRepository clockRepository;

    @Value("${admin.account}")
    private String adminAccount;

    @Value("${user.account}")
    private String userAccount;

    @Value("${user.password}")
    private String userPassword;

    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    public User findByUsernameAndEmail(String username, String email) {
        return userRepository.findByUsernameAndEmail(username, email);
    }

    public User save(User user) {
        return userRepository.save(user);
    }

    public User update(User user) {
        return userRepository.save(user);
    }

    public User findByUsernameOrEmail(String username, String email) {
        return userRepository.findByUsernameOrEmail(username, email);
    }

    public User findByEmail(String email) {
        return userRepository.findByEmail(email);
    }

    public User findById(Integer id) {
        return userRepository.findById(id).orElse(null);
    }

    public Map<String, Object> findUserData(Integer userId) {
        User user = userRepository.findById(userId).orElseThrow(() -> new BizException("用户不存在"));
        Date now = new Date();
        if (user.getRegisterTime() == null) {
            user.setRegisterTime(now);
            userRepository.save(user);
        }
        if (user.getLastOnlineTime() == null) {
            user.setLastOnlineTime(now);
            userRepository.save(user);
        }
        Map<String, Object> data = new HashMap<>();
        data.put("clickCount", Optional.ofNullable(user.getClickCount()).orElse(0));
        data.put("searchCount", Optional.ofNullable(user.getSearchCount()).orElse(0));
        data.put("registerDay", DateUtil.between(user.getRegisterTime(), now, DateUnit.DAY) + 1);
        data.put("onlineHour", Optional.ofNullable(user.getOnlineHour()).orElse(0) + 1);
        data.put("categoryCount", categoryRepository.countByUserId(userId));
        data.put("favoriteCount", favoritesRepository.countByUserIdAndDeleteFlag(userId, 0));
        data.put("momentCount", momentRepository.countByUserId(userId));
        data.put("taskCount", taskRepository.countByUserId(userId));
        data.put("navigationCount", quickNavigationRepository.countByUserId(userId));
        data.put("memorandumCount", memorandumRepository.countByUserId(userId));
        data.put("searchTypeCount", searchTypeRepository.countByUserId(userId));
        data.put("fileCount", userFileRepository.countByUserIdAndIsDir(userId, 0));
        data.put("shareCount", favoritesRepository.countByUserIdAndIsShare(userId, PublicConstants.SHARE_CODE));
        data.put("recycleCount", favoritesRepository.countByUserIdAndDeleteFlag(userId, PublicConstants.DELETE_CODE));
        data.put("clockCount", clockRepository.countByUserId(userId));
        return data;
    }

    public void deleteAllData(Integer userId) {
        categoryRepository.deleteAllByUserId(userId);
        favoritesRepository.deleteAllByUserId(userId);
        passwordRepository.deleteAllByUserId(userId);
        momentRepository.deleteAllByUserId(userId);
        taskRepository.deleteAllByUserId(userId);
        searchTypeRepository.deleteAllByUserId(userId);
        quickNavigationRepository.deleteAllByUserId(userId);
        memorandumRepository.deleteAllByUserId(userId);
        initData(userId);
    }

    public void initData(Integer userId) {
        // 创建默认分类
        String name = PublicConstants.DEFAULT_CATEGORY_NAME;
        Category category = new Category().setName(name).setUserId(userId).setIsSystem(PublicConstants.SYSTEM_CATEGORY_CODE).setSort(PublicConstants.MAX_SORT_NUMBER).setPinyin(PinYinUtils.toPinyin(name)).setPinyinS(PinYinUtils.toPinyinS(name));
        categoryRepository.save(category);
        // 推荐收藏
        List<Favorites> favorites = appConfig.getRecommends().stream().map(str -> {
            String[] split = str.split(PublicConstants.DEFAULT_DELIMITER);
            return new Favorites().setName(split[0]).setIcon(split[1] + "/favicon.ico").setUrl(split[1]).setCategoryId(category.getId()).setUserId(userId).setPinyin(PinYinUtils.toPinyin(split[0])).setPinyinS(PinYinUtils.toPinyinS(split[0]));
        }).collect(Collectors.toList());
        favoritesRepository.saveAll(favorites);
        // 推荐搜索
        List<SearchType> searchTypes = appConfig.getSearches().stream().map(str -> {
            String[] split = str.split(PublicConstants.DEFAULT_DELIMITER);
            return new SearchType().setName(split[0]).setIcon(split[1]).setUrl(split[2]).setUserId(userId);
        }).collect(Collectors.toList());
        searchTypeRepository.saveAll(searchTypes);
    }

    public void shareCancel(Integer id) {
        UserFile userFile = userFileRepository.findById(id).orElse(null);
        if (userFile != null) {
            userFile.setShareId(null);
            userFileRepository.save(userFile);
        }
    }

    public void deleteById(Integer id) {
        userRepository.deleteById(id);
    }

    public User findByNickName(String name) {
        return userRepository.findByNickName(name);
    }

    public PageInfo<User> findUserPageList(String name, Integer pageNum, Integer pageSize) {
        String text = SqlUtils.trimAndEscape(name);
        List<Sort.Order> orders = new ArrayList<>();
        orders.add(new Sort.Order(Sort.Direction.DESC, "registerTime"));
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(orders));
        // 构造自定义查询条件
        Specification<User> queryCondition = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();
            predicateList.add(criteriaBuilder.equal(root.get("isAdmin"), 0));
            if (StrUtil.isNotBlank(text)) {
                predicateList.add(criteriaBuilder.or(criteriaBuilder.like(root.get("username"), "%" + text + "%"), criteriaBuilder.like(root.get("nickName"), "%" + text + "%"), criteriaBuilder.like(root.get("email"), "%" + text + "%")));
            }
            return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
        };
        Page<User> page = userRepository.findAll(queryCondition, pageable);
        List<User> list = page.getContent().stream().map(user -> {
            User user2 = new User();
            BeanUtils.copyProperties(user, user2);
            user2.setPassword(null);
            return user2;
        }).collect(Collectors.toList());
        return new PageInfo<>(list, page.getTotalPages(), page.getTotalElements());
    }

    public List<User> findAllById(List<Integer> ids) {
        return userRepository.findAllById(ids);
    }

    public PageInfo<User> findAdminPageList(String name, Integer pageNum, Integer pageSize) {
        String text = SqlUtils.trimAndEscape(name);
        List<Sort.Order> orders = new ArrayList<>();
        orders.add(new Sort.Order(Sort.Direction.DESC, "registerTime"));
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(orders));
        // 构造自定义查询条件
        Specification<User> queryCondition = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();
            predicateList.add(criteriaBuilder.equal(root.get("isAdmin"), 1));
            if (StrUtil.isNotBlank(text)) {
                predicateList.add(criteriaBuilder.or(criteriaBuilder.like(root.get("username"), "%" + text + "%"), criteriaBuilder.like(root.get("nickName"), "%" + text + "%"), criteriaBuilder.like(root.get("email"), "%" + text + "%")));
            }
            return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
        };
        Page<User> page = userRepository.findAll(queryCondition, pageable);
        List<User> list = page.getContent().stream().map(user -> {
            User user1 = new User();
            BeanUtils.copyProperties(user, user1);
            user1.setPassword(null);
            return user1;
        }).collect(Collectors.toList());
        return new PageInfo<>(list, page.getTotalPages(), page.getTotalElements());
    }

    public List<String> findUserEmails() {
        return userRepository.findUserEmails();
    }

    public Boolean resetSort(Integer userId) {
        categoryRepository.resetSort(userId);
        favoritesRepository.resetSort(userId);
        return true;
    }

    public Integer enableUser(Integer id) {
        User user = userRepository.findById(id).orElse(null);
        AssertUtils.notNull(user, "账号不存在");
        AssertUtils.isTrue(!Objects.equals(user.getIsAdmin(), 1), "管理员账号禁止修改");
        boolean disable = Objects.equals(user.getStatus(), 3);
        user.setStatus(disable ? 1 : 3);
        userRepository.save(user);
        SecurityUser currentUser = ContextUtils.getCurrentUser();
        systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(IpUtils.getIp()).setContent(disable ? String.format(LogConstants.ACCOUNT_ENABLE_MSG, currentUser.getUsername(), user.getUsername()) : String.format(LogConstants.ACCOUNT_DISABLE_MSG, currentUser.getUsername(), user.getUsername())));
        return user.getStatus();
    }

    public Boolean sendNotice(SendNoticeDto dto) {
        List<User> list = userRepository.findAllById(dto.getIds());
        SecurityUser currentUser = ContextUtils.getCurrentUser();
        Lists.partition(list, Optional.ofNullable(appConfig.getMailBatchSend()).orElse(10)).forEach(list1 -> {
            String emails = list1.stream().map(User::getEmail).collect(Collectors.joining(PublicConstants.DEFAULT_DELIMITER));
            emailUtils.sendHtmlMail(emails, EmailConstants.ADMIN_NOTICE_TITLE, dto.getContent().replaceAll("\n", "<br>"));
            systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(IpUtils.getIp()).setContent(String.format(LogConstants.NOTICE_SEND_MSG, currentUser.getUsername(), emails, dto.getContent())));
        });
        return true;
    }

    public Integer setUserPwd(Integer id) {
        User user = userRepository.findById(id).orElse(null);
        AssertUtils.notNull(user, "账号不存在");
        AssertUtils.isTrue(!Objects.equals(user.getIsAdmin(), 1), "管理员账号禁止修改");
        if (Objects.equals(user.getUsername(), userAccount)) {
            user.setPassword(passwordEncoder.encode(DigestUtils.md5DigestAsHex(userPassword.getBytes(StandardCharsets.UTF_8))));
            userRepository.save(user);
        } else {
            String tempPwd = RandomUtil.randomString(PublicConstants.TEMP_PASSWORD_LENGTH);
            // 重置用户密码
            user.setPassword(passwordEncoder.encode(DigestUtils.md5DigestAsHex(tempPwd.getBytes(StandardCharsets.UTF_8))));
            userRepository.save(user);
            // 将临时密码发送至用户邮箱
            emailUtils.sendSimpleMail(user.getEmail(), EmailConstants.PASSWORD_RESET_TITLE, String.format(EmailConstants.PASSWORD_RESET_CONTENT, tempPwd));
        }
        SecurityUser currentUser = ContextUtils.getCurrentUser();
        systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(IpUtils.getIp()).setContent(String.format(LogConstants.RESET_PWD_MSG, currentUser.getUsername(), user.getUsername())));
        return user.getStatus();
    }

    public Boolean delete(Integer id) {
        User user = userRepository.findById(id).orElse(null);
        AssertUtils.notNull(user, "账号不存在");
        AssertUtils.isTrue(!Objects.equals(user.getUsername(), adminAccount), "超级管理员账号禁止删除");
        userRepository.deleteById(user.getId());
        SecurityUser currentUser = ContextUtils.getCurrentUser();
        systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(IpUtils.getIp()).setContent(String.format(LogConstants.ADMIN_DELETE_MSG, currentUser.getUsername(), user.getUsername())));
        return true;
    }

    public Boolean saveUser(User user) {
        User username = userRepository.findByUsername(user.getUsername());
        User email = userRepository.findByEmail(user.getEmail());
        AssertUtils.isNull(username, "用户已存在");
        AssertUtils.isNull(email, "邮箱已被使用");
        user.setIsAdmin(1).setPassword(passwordEncoder.encode(user.getPassword())).setRegisterTime(new Date());
        userRepository.save(user);
        SecurityUser currentUser = ContextUtils.getCurrentUser();
        systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(IpUtils.getIp()).setContent(String.format(LogConstants.ADMIN_SAVE_MSG, currentUser.getUsername(), user.getUsername())));
        return true;
    }

    public Boolean updateUser(User user) {
        User user1 = userRepository.findById(user.getId()).orElse(null);
        AssertUtils.notNull(user1, "账号不存在");
        User email = userRepository.findByEmail(user.getEmail());
        AssertUtils.isTrue(email == null || Objects.equals(email.getId(), user1.getId()), "邮箱已被使用");
        user1.setEmail(user.getEmail());
        user1.setNickName(user.getNickName());
        userRepository.save(user1);
        SecurityUser currentUser = ContextUtils.getCurrentUser();
        systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(IpUtils.getIp()).setContent(String.format(LogConstants.ADMIN_UPDATE_MSG, currentUser.getUsername(), user1.getUsername())));
        return true;
    }

    public Integer enableAdmin(Integer id) {
        User user = userRepository.findById(id).orElse(null);
        AssertUtils.notNull(user, "账号不存在");
        AssertUtils.isTrue(!Objects.equals(user.getUsername(), adminAccount), "超级管理员账号禁止修改");
        boolean disable = Objects.equals(user.getStatus(), 3);
        user.setStatus(disable ? 1 : 3);
        userRepository.save(user);
        SecurityUser currentUser = ContextUtils.getCurrentUser();
        systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(IpUtils.getIp()).setContent(disable ? String.format(LogConstants.ACCOUNT_ENABLE_MSG, currentUser.getUsername(), user.getUsername()) : String.format(LogConstants.ACCOUNT_DISABLE_MSG, currentUser.getUsername(), user.getUsername())));
        return user.getStatus();
    }

    public Integer setAdminPwd(Integer id, String pwd) {
        User user = userRepository.findById(id).orElse(null);
        AssertUtils.notNull(user, "账号不存在");
        AssertUtils.isTrue(!Objects.equals(user.getUsername(), adminAccount), "超级管理员账号禁止修改");
        user.setPassword(passwordEncoder.encode(pwd));
        userRepository.save(user);
        SecurityUser currentUser = ContextUtils.getCurrentUser();
        systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(IpUtils.getIp()).setContent(String.format(LogConstants.RESET_ADMIN_PWD_MSG, currentUser.getUsername(), user.getUsername())));
        return user.getStatus();
    }

    public Boolean existUsername(String username) {
        return userRepository.findByUsername(username) != null;
    }

    public Boolean existEmail(String email) {
        return userRepository.findByEmail(email) != null;
    }

    public User authInfo() {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "请先登录");
        User user1 = new User();
        BeanUtils.copyProperties(user, user1);
        user1.setPassword(null);
        user1.setPermissions(securityUser.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList());
        return user1;
    }

    public Boolean logout() {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(IpUtils.getIp()).setContent(String.format(LogConstants.LOGOUT_MSG, securityUser.getUsername())));
        return true;
    }

    public Boolean updateUserInfo(UserInfoUpdateDto dto) {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "账号不存在");
        AssertUtils.isTrue(!Objects.equals(user.getUsername(), userAccount), "演示账号禁止修改");
        Verification verification = verificationService.findCode(dto.getEmail(), PublicConstants.VERIFICATION_EMAIL_USER_UPDATE);
        String emailCode = Optional.ofNullable(verification).filter(verification1 -> verification1.getExpiredTime().getTime() > System.currentTimeMillis()).map(Verification::getCode).orElse(null);
        AssertUtils.isTrue(StrUtil.isNotBlank(emailCode), "验证码已失效");
        User user1 = userRepository.findByEmail(dto.getEmail());
        AssertUtils.isTrue(user1 == null || Objects.equals(user1.getId(), user.getId()), "邮箱已被使用");
        user.setEmail(dto.getEmail());
        user.setNickName(dto.getNickName());
        userRepository.save(user);
        // 移除验证码
        verificationService.deleteById(verification.getId());
        return true;
    }

    public void visitAdd() {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "用户不存在");
        user.setClickCount(Optional.ofNullable(user.getClickCount()).orElse(0) + 1);
        userRepository.save(user);
    }

    public void searchAdd() {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "用户不存在");
        user.setSearchCount(Optional.ofNullable(user.getSearchCount()).orElse(0) + 1);
        userRepository.save(user);
    }

    public void onlineAdd() {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "用户不存在");
        Date now = new Date();
        if (user.getRegisterTime() == null) {
            user.setRegisterTime(now);
        }
        if (user.getOnlineHour() == null) {
            user.setLastOnlineTime(now);
        }
        long between = DateUtil.between(Optional.ofNullable(user.getLastOnlineTime()).orElse(now), now, DateUnit.HOUR);
        if (between > 0) {
            user.setOnlineHour(Optional.ofNullable(user.getOnlineHour()).orElse(0) + 1);
            user.setLastOnlineTime(now);
        }
        userRepository.save(user);
    }

    public void updateSettings(UserSettingDto dto) {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "用户不存在");
        Optional.ofNullable(dto.getSearchStyle()).ifPresent(user::setSearchStyle);
        Optional.ofNullable(dto.getFavoriteLink()).ifPresent(user::setFavoriteLink);
        Optional.ofNullable(dto.getSmartAi()).ifPresent(user::setSmartAi);
        userRepository.save(user);
    }

    public void pwdCode() {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "用户不存在");
        AssertUtils.isTrue(!Objects.equals(user.getUsername(), userAccount), "演示账号禁止修改");
        String email = user.getEmail();
        AssertUtils.isTrue(verificationService.sendEnable(email, PublicConstants.VERIFICATION_EMAIL_USER_UPDATE), "发送验证码太频繁");
        String code = RandomUtil.randomNumbers(PublicConstants.RANDOM_CODE_LENGTH);
        Date expTime = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(appConfig.getVerificationExpiredMinutes()));
        Verification verification = new Verification().setAccount(email).setCode(code).setExpiredTime(expTime).setAction(PublicConstants.VERIFICATION_EMAIL_CHANGE_PWD).setSendTime(new Date());
        verificationService.save(verification);
        emailUtils.sendSimpleMail(email, EmailConstants.CHANGE_PWD_TITLE, String.format(EmailConstants.CHANGE_PWD_CONTENT, code, appConfig.getVerificationExpiredMinutes()));
    }

    public void saveNickName(User user) {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user1 = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user1, "用户不存在");
        AssertUtils.isTrue(!Objects.equals(user1.getUsername(), userAccount), "演示账号禁止修改");
        AssertUtils.isTrue(StrUtil.isNotBlank(user.getNickName()), "昵称不能为空");
        user1.setNickName(user.getNickName());
        userRepository.save(user1);
    }

    public void feedback(Feedback feedback) {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        Feedback feedback1 = feedbackRepository.findTopByAccountOrderByCreateTimeDesc(securityUser.getUsername());
        AssertUtils.isTrue(feedback1 == null || DateUtil.between(feedback1.getCreateTime(), new Date(), DateUnit.SECOND) > appConfig.getFeedbackInterval(), ErrorConstants.FEEDBACK_INTERVAL_MSG);
        feedback.setAccount(securityUser.getUsername());
        feedbackRepository.save(feedback);
    }

    public Boolean existNickName(String name) {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = findByNickName(name);
        return user != null && !user.getId().equals(securityUser.getId());
    }

    public void cleanData(String loginPwd) {
        SecurityUser user = ContextUtils.getCurrentUser();
        AssertUtils.isTrue(passwordEncoder.matches(loginPwd, user.getPassword()), ErrorConstants.INVALID_PWD_MSG);
        deleteAllData(user.getId());
    }

    public void updateUserInfoCode(String email) {
        AssertUtils.isTrue(verificationService.sendEnable(email, PublicConstants.VERIFICATION_EMAIL_USER_UPDATE), "发送验证码太频繁");
        String code = RandomUtil.randomNumbers(PublicConstants.RANDOM_CODE_LENGTH);
        Date expTime = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(appConfig.getVerificationExpiredMinutes()));
        Verification verification = new Verification().setAccount(email).setCode(code).setExpiredTime(expTime).setAction(PublicConstants.VERIFICATION_EMAIL_USER_UPDATE).setSendTime(new Date());
        verificationService.save(verification);
        emailUtils.sendSimpleMail(email, EmailConstants.USER_UPDATE_TITLE, String.format(EmailConstants.USER_UPDATE_CONTENT, code, appConfig.getVerificationExpiredMinutes()));
    }

    public void updateViewStyle(Integer viewStyle) {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "用户不存在");
        user.setViewStyle(viewStyle == 1 ? 1 : 0);
        userRepository.save(user);
    }

    public void resetPwd(String code, String newPassword) {
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "用户不存在");
        AssertUtils.isTrue(!Objects.equals(user.getUsername(), userAccount), "演示账号禁止修改");
        Verification verification = verificationService.findCode(user.getEmail(), PublicConstants.VERIFICATION_EMAIL_CHANGE_PWD);
        String emailCode = Optional.ofNullable(verification).filter(verification1 -> verification1.getExpiredTime().getTime() > System.currentTimeMillis()).map(Verification::getCode).orElse(null);
        AssertUtils.isTrue(StrUtil.isNotBlank(emailCode) && Objects.equals(code, emailCode), ErrorConstants.INVALID_VERIFICATION_MSG);
        user.setPassword(passwordEncoder.encode(newPassword));
        userRepository.save(user);
        // 移除验证码
        verificationService.deleteById(verification.getId());
    }

    public void registerCode(String email) {
        AssertUtils.isTrue(verificationService.sendEnable(email, PublicConstants.VERIFICATION_REGISTER), "发送验证码太频繁");
        String code = RandomUtil.randomNumbers(PublicConstants.RANDOM_CODE_LENGTH);
        Date expTime = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(appConfig.getVerificationExpiredMinutes()));
        Verification verification = new Verification().setAccount(email).setCode(code).setExpiredTime(expTime).setAction(PublicConstants.VERIFICATION_REGISTER).setSendTime(new Date());
        verificationService.save(verification);
        log.info("注册用户：email = {}, code = {}", email, code);
        emailUtils.sendSimpleMail(email, EmailConstants.REGISTER_TITLE, String.format(EmailConstants.REGISTER_CONTENT, code, appConfig.getVerificationExpiredMinutes()));
    }

    public String registerUser(User user) {
        Verification verification = verificationService.findCode(user.getEmail(), PublicConstants.VERIFICATION_REGISTER);
        String code = Optional.ofNullable(verification).filter(verification1 -> verification1.getExpiredTime().getTime() > System.currentTimeMillis()).map(Verification::getCode).orElse(null);
        AssertUtils.isTrue(StrUtil.isNotBlank(code) && user.getCode().equals(code), ErrorConstants.INVALID_VERIFICATION_MSG);
        User user2 = userRepository.findByUsernameOrEmail(user.getUsername(), user.getEmail());
        AssertUtils.isNull(user2, ErrorConstants.USERNAME_OR_EMAIL_EXISTED_MSG);
        Date now = new Date();
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        user.setCapacity(appConfig.getInitCapacity() * 1024 * 1024L);
        user.setRegisterTime(now);
        user.setLastOnlineTime(now);
        User user1 = userRepository.save(user);
        // 初始化用户数据
        initData(user1.getId());
        // 生成token
        String token = tokenUtils.createToken(user1.getUsername());
        // 移除验证码
        verificationService.deleteById(verification.getId());
        return token;
    }

    public void emailLoginCode(String email) {
        AssertUtils.isTrue(verificationService.sendEnable(email, PublicConstants.VERIFICATION_EMAIL_LOGIN), "发送验证码太频繁");
        String code = RandomUtil.randomNumbers(PublicConstants.RANDOM_CODE_LENGTH);
        Date expTime = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(appConfig.getVerificationExpiredMinutes()));
        Verification verification = new Verification().setAccount(email).setCode(code).setExpiredTime(expTime).setAction(PublicConstants.VERIFICATION_EMAIL_LOGIN).setSendTime(new Date());
        verificationService.save(verification);
        log.info("邮箱登录：email = {}, code = {}", email, code);
        emailUtils.sendSimpleMail(email, EmailConstants.LOGIN_TITLE, String.format(EmailConstants.LOGIN_CONTENT, code, appConfig.getVerificationExpiredMinutes()));
    }

    public UserLoginDto emailLogin(String email, String code) {
        Verification verification = verificationService.findCode(email, PublicConstants.VERIFICATION_EMAIL_LOGIN);
        String loginEmailCode = Optional.ofNullable(verification).filter(verification1 -> verification1.getExpiredTime().getTime() > System.currentTimeMillis()).map(Verification::getCode).orElse(null);
        AssertUtils.isTrue(StrUtil.isNotBlank(loginEmailCode) && loginEmailCode.equals(code), ErrorConstants.INVALID_VERIFICATION_MSG);
        // 查询email是否注册，如果没有注册，先注册账号
        User user = userRepository.findByEmail(email);
        if (user == null) {
            // 注册用户
            Date now = new Date();
            String tempPwd = RandomUtil.randomString(PublicConstants.TEMP_PASSWORD_LENGTH);
            user = new User();
            user.setUsername(email);
            user.setPassword(passwordEncoder.encode(DigestUtils.md5DigestAsHex(tempPwd.getBytes(StandardCharsets.UTF_8))));
            user.setEmail(email);
            user.setCapacity(appConfig.getInitCapacity() * 1024 * 1024L);
            user.setRegisterTime(now);
            user.setLastOnlineTime(now);
            user = userRepository.save(user);
            // 创建默认分类
            Category category = new Category().setName(PublicConstants.DEFAULT_CATEGORY_NAME).setUserId(user.getId()).setIsSystem(PublicConstants.SYSTEM_CATEGORY_CODE).setSort(PublicConstants.MAX_SORT_NUMBER).setPinyin(PinYinUtils.toPinyin(PublicConstants.DEFAULT_CATEGORY_NAME)).setPinyinS(PinYinUtils.toPinyinS(PublicConstants.DEFAULT_CATEGORY_NAME));
            categoryRepository.save(category);
            // 推荐收藏
            Integer userId = user.getId();
            appConfig.getRecommends().forEach(str -> {
                String[] split = str.split(PublicConstants.DEFAULT_DELIMITER);
                Favorites favorites = new Favorites().setName(split[0]).setIcon(split[1] + "/favicon.ico").setUrl(split[1]).setCategoryId(category.getId()).setUserId(userId).setPinyin(PinYinUtils.toPinyin(split[0])).setPinyinS(PinYinUtils.toPinyinS(split[0]));
                favoritesRepository.save(favorites);
            });
            // 发送邮件
            emailUtils.sendSimpleMail(user.getEmail(), EmailConstants.EMAIL_REGISTER_TITLE, String.format(EmailConstants.EMAIL_REGISTER_CONTENT, user.getUsername(), tempPwd));
        }
        AssertUtils.isTrue(!Objects.equals(user.getStatus(), 3), ErrorConstants.USER_DISABLED_MSG);
        updateUserStatus(user, true);
        // 生成token
        String token = tokenUtils.createToken(user.getUsername(), TimeUnit.DAYS.toMillis(PublicConstants.REMEMBER_ME_DAYS));
        return new UserLoginDto().setAccessToken(token).setAdmin(Objects.equals(user.getIsAdmin(), 1));

    }

    private void updateUserStatus(User user, boolean success) {
        int errorCount = Optional.ofNullable(user.getErrorCount()).orElse(0);
        if (success) {
            if (errorCount > 0) {
                user.setErrorCount(0);
            }
            if (user.getStatus() == 2) {
                user.setStatus(1);
            }
            user.setLastOnlineTime(new Date());
            if (Objects.equals(user.getIsAdmin(), 1)) {
                String ip = IpUtils.getIp();
                systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(ip).setContent(String.format(LogConstants.LOGIN_SUCCESS_MSG, user.getUsername())));
            }
        } else {
            if (Objects.equals(user.getIsAdmin(), 1)) {
                String ip = IpUtils.getIp();
                systemLogRepository.save(new SystemLog().setCreateTime(new Date()).setIp(ip).setContent(String.format(LogConstants.LOGIN_FAILED_MSG, user.getUsername())));
            }
            user.setErrorCount(errorCount + 1);
            if (user.getErrorCount() > appConfig.getErrorCountLimit() && !Objects.equals(user.getUsername(), userAccount) && !Objects.equals(user.getUsername(), adminAccount)) {
                emailUtils.sendSimpleMail(user.getEmail(), EmailConstants.SAFE_NOTICE_TITLE, String.format(EmailConstants.SAFE_NOTICE_CONTENT, user.getUsername()));
                user.setErrorCount(0);
                user.setStatus(2);
            }
        }
        userRepository.save(user);
    }

    public UserLoginDto login(User user, String remember) {
        AssertUtils.isTrue(CaptchaUtils.verify(user.getSid(), user.getCode()), "验证码错误");
        User user1 = userRepository.findByUsername(user.getUsername());
        AssertUtils.notNull(user1, ErrorConstants.INVALID_USERNAME_MSG);
        if (StrUtil.isNotBlank(user.getPassword()) && passwordEncoder.matches(user.getPassword(), user1.getPassword())) {
            AssertUtils.isTrue(!Objects.equals(user1.getStatus(), 3), ErrorConstants.USER_DISABLED_MSG);
            AssertUtils.isTrue(!Objects.equals(user1.getStatus(), 2), ErrorConstants.USER_LOCKED_MSG);
            String token;
            if (PublicConstants.REMEMBER_ME_CODE.equals(remember)) {
                token = tokenUtils.createToken(user1.getUsername(), TimeUnit.DAYS.toMillis(PublicConstants.REMEMBER_ME_DAYS));
            } else {
                token = tokenUtils.createToken(user1.getUsername());
            }
            updateUserStatus(user1, true);
            return new UserLoginDto().setAccessToken(token).setAdmin(Objects.equals(user1.getIsAdmin(), 1));
        } else {
            updateUserStatus(user1, false);
            throw new NoRollbackException(ErrorConstants.INVALID_USERNAME_OR_PASSWORD_MSG);
        }
    }

    public UserLoginDto qrLogin(User user) {
        AssertUtils.isFalse(StrUtil.isBlank(user.getSid()), ErrorConstants.SID_NOT_FOUND_MSG);
        WebSocketServer channel = WebSocketServer.findChannel(user.getSid());
        AssertUtils.notNull(channel, ErrorConstants.QRCODE_INVALID_MSG);
        User user1 = userRepository.findByUsername(user.getUsername());
        AssertUtils.notNull(user1, ErrorConstants.INVALID_USERNAME_MSG);
        if (StrUtil.isNotBlank(user.getPassword()) && passwordEncoder.matches(user.getPassword(), user1.getPassword())) {
            AssertUtils.isTrue(!Objects.equals(user1.getStatus(), 3), ErrorConstants.USER_DISABLED_MSG);
            AssertUtils.isTrue(!Objects.equals(user1.getStatus(), 2), ErrorConstants.USER_LOCKED_MSG);
            String token = tokenUtils.createToken(user1.getUsername());
            UserLoginDto dto = new UserLoginDto().setAccessToken(token).setAdmin(Objects.equals(user1.getIsAdmin(), 1));
            channel.sendMessage(JSONUtil.toJsonStr(ApiResponse.ok(dto)));
            updateUserStatus(user1, true);
            return dto;
        } else {
            // 记录失败次数
            updateUserStatus(user1, false);
            throw new NoRollbackException(ErrorConstants.INVALID_USERNAME_OR_PASSWORD_MSG);
        }
    }

    public void forgotCode(String email) {
        AssertUtils.isTrue(verificationService.sendEnable(email, PublicConstants.VERIFICATION_EMAIL_FORGOT), "发送验证码太频繁");
        String code = RandomUtil.randomNumbers(PublicConstants.RANDOM_CODE_LENGTH);
        Date expTime = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(appConfig.getVerificationExpiredMinutes()));
        Verification verification = new Verification().setAccount(email).setCode(code).setExpiredTime(expTime).setAction(PublicConstants.VERIFICATION_EMAIL_FORGOT).setSendTime(new Date());
        verificationService.save(verification);
        log.info("忘记密码：email = {}, code = {}", email, code);
        emailUtils.sendSimpleMail(email, EmailConstants.FORGOT_TITLE, String.format(EmailConstants.FORGOT_CONTENT, code, appConfig.getVerificationExpiredMinutes()));
    }

    public void forgot(User user) {
        Verification verification = verificationService.findCode(user.getEmail(), PublicConstants.VERIFICATION_EMAIL_FORGOT);
        String forgotEmailCode = Optional.ofNullable(verification).filter(verification1 -> verification1.getExpiredTime().getTime() > System.currentTimeMillis()).map(Verification::getCode).orElse(null);
        AssertUtils.isTrue(StrUtil.isNotBlank(forgotEmailCode) && forgotEmailCode.equals(user.getCode()), ErrorConstants.INVALID_VERIFICATION_MSG);
        User user1 = userRepository.findByUsernameAndEmail(user.getUsername(), user.getEmail());
        AssertUtils.notNull(user1, ErrorConstants.INVALID_USERNAME_OR_EMAIL_MSG);
        AssertUtils.isTrue(!Objects.equals(user1.getStatus(), 3), ErrorConstants.USER_DISABLED_MSG);
        String tempPwd = RandomUtil.randomString(PublicConstants.TEMP_PASSWORD_LENGTH);
        // 重置用户密码
        user1.setStatus(1);
        user1.setPassword(passwordEncoder.encode(DigestUtils.md5DigestAsHex(tempPwd.getBytes(StandardCharsets.UTF_8))));
        userRepository.save(user1);
        // 将临时密码发送至用户邮箱
        emailUtils.sendSimpleMail(user1.getEmail(), EmailConstants.PASSWORD_RESET_TITLE, String.format(EmailConstants.PASSWORD_RESET_CONTENT, tempPwd));
    }

    public UserCapacityDto findCapacity() {
        SecurityUser user = ContextUtils.getCurrentUser();
        User user1 = userRepository.findById(user.getId()).orElseThrow(() -> new BizException("用户不存在"));
        return new UserCapacityDto().setId(user1.getId()).setCapacity(Optional.ofNullable(user1.getCapacity()).orElse(0L)).setUsedSize(Optional.ofNullable(user1.getUsedSize()).orElse(0L));
    }
}
